﻿/*
 * EquirectangularProjection
 * 
 * initial implementation : 20.12.2008
 *
 * Copyright (c) Adam Sida, asida@centrum.cz.
 * All rights reserved.
 * 
 * for license see Petra.Gis.License.txt
 */
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Petra.Gis.Geometry.Support;
using Petra.Gis.ReferenceSystemServices;


namespace Petra.Gis.ReferenceSystemServices.Transformations
{
  /// <summary>
  /// The equirectangular projection (also called the equidistant cylindrical projection,
  /// geographic projection, plate carré or carte parallelogrammatique projection or CPP)
  /// </summary>
  /// <remarks>
  /// <para>The projection is neither equal area nor conformal. 
  /// Because of the distortions introduced by this projection, it has little use in navigation 
  /// or cadastral mapping and finds its main use in thematic mapping. 
  /// In particular, the plate carrée has become a de-facto standard for computer applications 
  /// that process global maps, such as Celestia and NASA World Wind, 
  /// because of the connection between an image pixel and its geographic position 
  /// <see cref="http://en.wikipedia.org/wiki/Plate_carr%C3%A9e_projection"/>
  /// </para>
  /// </remarks>
  [DataContract(Name="TRANSFORM")]
  public class EquirectangularProjection : ProjectionTransform
  {

    private double _firstStandardParalel;
    private double _longitudeShift;
    private double _cosFirstStandardParalel;


    /// <summary>
    /// xaml constr.
    /// </summary>
    public EquirectangularProjection()
      :base()
    {  }





    /// <summary>
    /// Initializes the EquirectangularProjection object with the specified parameters to project points. 
    /// </summary>
    /// <param name="proj">projected coordinate system with s-jtsk projection</param>
    /// <remarks>
    /// </remarks>
    public EquirectangularProjection(ProjectedCoordinateSystem proj)
      : this(proj, false) { }






    /// <summary>
    /// Initializes the EquirectangularProjection object with the specified parameters.
    /// </summary>
    /// <param name="parameters">List of parameters to initialize the projection.</param>
    /// <param name="isInverse">Indicates whether the projection forward (meters to degrees or degrees to meters).</param>
    /// <remarks>
    /// <para>The parameters this projection expects are listed below.</para>
    /// <list type="table">
    /// <listheader><term>Items</term><description>Descriptions</description></listheader>
    /// <item><term>central_meridian</term><description>The longitude of the point from which the values of both the geographical coordinates on the ellipsoid and the grid coordinates on the projection are deemed to increment or decrement for computational purposes. Alternatively it may be considered as the longitude of the point which in the absence of application of false coordinates has grid coordinates of (0,0).</description></item>
    /// <item><term>latitude_of_origin</term><description>The latitude of the point from which the values of both the geographical coordinates on the ellipsoid and the grid coordinates on the projection are deemed to increment or decrement for computational purposes. Alternatively it may be considered as the latitude of the point which in the absence of application of false coordinates has grid coordinates of (0,0).</description></item>
    /// </list>
    /// </remarks>
    public EquirectangularProjection(ProjectedCoordinateSystem proj, bool isInverse)
      : base(proj, isInverse)
    {  }






    ///// <summary>
    ///// Equirectangular projection axis list
    ///// </summary>
    //public override AxisList Axes
    //{
    //  get
    //  {
    //    // this is default Equirectangular axis list with correct orientation
    //    // this orientation must remain as defined because of projection equations
    //    return new AxisList()
    //    {
    //      new Axis("X", Axis.East),
    //      new Axis("Y", Axis.North)
    //    };
    //  }
    //}





    /// <summary>
    /// must override here, spheric conformal doesnt need parameter distance units (only angular)
    /// </summary>
    /// <returns></returns>
    protected override bool graphCompleted()
    {
      return !(this.AngularParameterUnit == null || !parametersValid());
    }




    protected override bool parametersValid()
    {
      //no required params here
      return true;
    }





    protected override void updateTransform()
    {

      //if (_projCS.Projection.ClassName.ToLower() != "equirectangular")
      //  throw new ArgumentException("invalid projected coordinate system, must have Equirectangular projection");

      if (Projection.Unit != DistanceUnit.ProjectedDegree)
        throw new ArgumentException("projection must have ProjectedDegree as projection units");

      Metadata = Projection.ProjectionDef.Metadata;

      // init projection parameters
      _firstStandardParalel = 0;
      _longitudeShift = 0;

      // try get optional parameters from projection definition
      if (Parameters.ContainsKey("standard_parallel_1"))
        _firstStandardParalel = Parameters["standard_parallel_1"] * AngularParameterUnit.RadiansPerUnit;

      if (Parameters.ContainsKey("central_meridian"))
        _longitudeShift = Parameters["central_meridian"] * AngularParameterUnit.RadiansPerUnit;

      if (_longitudeShift != 0)
        throw new NotImplementedException("sorry longitude shift is not currently suported");

      _cosFirstStandardParalel = Math.Cos(_firstStandardParalel);

    }






    /// <summary>
    /// Converts coordinates in geographics cs to projected x,y (not meters).
    /// </summary>
    /// <param name="lonlat">The point in geog. cs units</param>
    /// <returns>Point in projected units (not meters)</returns>
    protected override void geographicToProjectedN(ICoordinate coord)
    {
      //coord.lon *= _geogUnit.RadiansPerUnit;
      //coord.lat *= _geogUnit.RadiansPerUnit;

      coord.X *= _cosFirstStandardParalel;
      //todo: change geometry when x shift

      // projection units are x,y but not meters
      // value is always degree but represented as DistanceUnits (projectedDegree) (not Angular)
      //coord.X /= _projUnit.MetersPerUnit;
      //coord.Y /= _projUnit.MetersPerUnit;
    }






    /// <summary>
    /// Converts coordinates in projected units to decimal degrees.
    /// </summary>
    /// <param name="p">Point in proj units in cartesian x,y</param>
    /// <returns>Transformed point in geogcs units</returns>
    protected override void projectedToGeographicN(ICoordinate coord)
    {

      //// convert back from DistanceUnit ( projectedDegree) to radians
      //coord.X *= _projUnit.MetersPerUnit;
      //coord.Y *= _projUnit.MetersPerUnit;

      // resolve first standard parallel
      coord.lon /= _cosFirstStandardParalel;
      //todo: change geometry when x shift

      //coord.X = radians2Degrees(coord.X);
      //coord.Y = radians2Degrees(coord.Y);

      //// convert to geogcs units
      //coord.lon /= _geogUnit.RadiansPerUnit;
      //coord.lat /= _geogUnit.RadiansPerUnit;
    }






    /// <summary>
    /// Returns the inverse of this projection.
    /// </summary>
    /// <returns>IMathTransform that is the reverse of the current projection.</returns>
    public override SRSTransform Inverse()
    {
      return new EquirectangularProjection(Projection, !IsInverse);
    }






    public override Dimension TransformDimension
    {
      get { return Dimension.dim2D; }
    }





    public static string[] ProjectionNames
    {
      get { return new string[] { "equirectangular", "cylindrical equirectangular", "plate carre" }; }
    }





    //new public static EquirectangularProjection Create(ProjectedCoordinateSystem pcs)
    public static SRSTransform Create(ProjectedCoordinateSystem pcs, bool getAsInverse)
    {
      return new EquirectangularProjection(pcs, getAsInverse);
    }


  }

}
